import type { Program } from 'estree'
import { analyze, Scope } from 'escope-id'

const specialWords = [
  'debugger',
  'with',
  'null',
  'undefined',
  'var',
  'let',
  'const',
  'function',
  'async',
  'await',
  'yield',
  'class',
  'extends',
  'new',
  'this',
  'super',
  'return',
  'break',
  'continue',
  'if',
  'else',
  'switch',
  'case',
  'for',
  'do',
  'while',
  'of',
  'in',
  'throw',
  'try',
  'catch',
  'finally',
  'typeof',
  'void',
  'delete',
  'instanceof',
  'import',
  'export',
  'default',
  'enum',
  'package',
  'public',
  'private',
  'protected',
  'interface',
  'implements',
  'static',
]

export type RetryFn = (old: string) => string;

function retryWith$ (old: string) {
  return '$' + old
}

export default class SafeIdGenerator {
  private retry: RetryFn
  private scope: Scope
  private retryTimes = 0
  private maxRetryTimes = 100
  private generatedNames: string[] = []

  constructor (program: Program, retry?: RetryFn) {
    this.scope = analyze(program)
    this.retry = retry || retryWith$
  }

  public setMaxRetryTimes (times: number) {
    this.maxRetryTimes = times
  }

  private isSafeInScope (scope: Scope, name: string) {
    const where = scope.where(name)
    if (scope === this.scope && where !== 'unknown') return false
    if (scope !== this.scope && where !== 'local' && where !== 'unknown')
      return false

    for (let i = 0; i < scope.children.length; i++) {
      if (!this.isSafeInScope(scope.children[i], name)) {
        return false
      }
    }

    return true
  }

  public isSafe (name: string) {
    if (specialWords.includes(name)) return false
    return this.isSafeInScope(this.scope, name)
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  public generate (_name: string | ((...args: any[]) => string)): string {
    const name = typeof _name === 'string' ? _name : _name()
    const generated = this.generatedNames.includes(name)
    if (!generated && this.isSafe(name)) {
      this.retryTimes = 0
      this.generatedNames.push(name)
      return name
    }
    const newName = this.retry(name)
    this.retryTimes++
    if (newName === name) {
      if (this.retryTimes === this.maxRetryTimes) {
        this.retryTimes = 0
        throw new Error('retry函数的调用次数已达最大')
      }
    }
    return this.generate(newName)
  }
}
